package lotusmonitor

import (
	"context"
	"encoding/hex"
	"encoding/json"
	"github.com/filecoin-project/go-state-types/abi"
	"github.com/filecoin-project/lotus/api"
	"github.com/filecoin-project/lotus/chain/types"
	"github.com/go-redis/redis/v8"
	"github.com/ipfs/go-cid"
	"lotusmonitor/logger"
	ltypes "lotusmonitor/types"
	"strconv"
	"time"
)



type Monitor struct {
	sync   *Syncer
	Lloger logger.Logger
	rdb    *redis.Client
}

func NewMonitor(config *SyncerConfig, rdb redis.Client) *Monitor {
	m := &Monitor{}
	m.sync = NewSyncer(config)
	m.rdb = &rdb
	l := logger.NewSTDLogger(logger.DebugLevel)
	m.Lloger = l
	return m
}

func (m *Monitor) SyncLoop(startHeight int64, mqTopic string) {
	m.SyncLoopAndCallback(startHeight,mqTopic, func(height int64) error { return nil })
}

func (m *Monitor) SyncLoopAndCallback(startHeight int64, mqTopic string, heightCall func(height int64) error) {
	conn, err := m.sync.Connection()
	if err != nil {
		m.Lloger.Errorf("sync connect error: %s\n",err)
		return
	}
	aapi := conn.API()
	var height = startHeight
	if startHeight < 0 {
		tipset, err := aapi.ChainHead(context.Background())
		if err != nil {
			m.Lloger.Errorf("sync fetch chain head error: %s\n", err)
			return
		}
		heightStr := tipset.Height().String()
		height, err = strconv.ParseInt(heightStr, 10, 64)
		if err != nil {
			m.Lloger.Errorf("sync fetch chain head error: %s\n", err)
			return
		}
	}
	for {
		startTime := time.Now().UnixNano() / 1e6
		m.Lloger.Infof("fetching chain height: %d\n", height)
		cids := m.waitForFetchTipSetByChainHeight(conn, height, 500)
		endTime := time.Now().UnixNano() / 1e6
		timeUsed := endTime - startTime
		m.Lloger.Debugf("fetched chain height success: %d, used: %dms\n",height, timeUsed)
		go m.chainTipSetHandler(conn, m.rdb, cids, mqTopic)
		err = heightCall(height)
		if err != nil {
			m.Lloger.Errorf("height call function err: %s\n", err)
		}
		height += 1
	}

}


func (m *Monitor) waitForFetchTipSetByChainHeight(conn *SyncerConnection, height int64, ms time.Duration) []cid.Cid {
	tipSet, err := m.fetchTipSetByChainHeight(conn, height)
	if err != nil {
		time.Sleep(ms * time.Millisecond)
		return m.waitForFetchTipSetByChainHeight(conn,height, ms)
	}
	return tipSet
}

func (m *Monitor) fetchTipSetByChainHeight(conn *SyncerConnection, height int64) ([]cid.Cid, error) {
	aapi := conn.API()
	var h = abi.ChainEpoch(height)
	ks := types.NewTipSetKey(cid.Cid{})
	tps, err := aapi.ChainGetTipSetByHeight(context.Background(),h,ks)
	if err != nil {
		return nil, err
	}
	return tps.Cids(), err
}
func (m *Monitor) fetchChainBlocks(conn *SyncerConnection, cIds []cid.Cid) []*api.BlockMessages {
	aapi := conn.API()
	var out = make([]*api.BlockMessages, 0)
	for _, id := range cIds {
		msg,err := aapi.ChainGetBlockMessages(context.Background(),id)
		if err != nil {
			m.Lloger.Errorf("fetch chain block by: %s, error: %s\n", id.String(), err)
			continue
		}
		out = append(out, msg)
	}
	return out
}

func (m *Monitor) chainTipSetHandler(conn *SyncerConnection, rdb *redis.Client, tipSet []cid.Cid, mqTopic string) {
	bls := m.fetchChainBlocks(conn, tipSet)
	for _, bl := range bls {
		//m.Lloger.Infof("handle chain tip set by height: %d\n", height)
		msgs := m.fetchBlockMessages(conn, bl.Cids)
		for _, msg := range msgs {
			ccid := ltypes.TipSetCID{ID: msg.Cid().String()}
			m.Lloger.Debugf("fetched message cid: %s\n", msg.Cid().String())
			outmsg := ltypes.Message{
				Version: msg.Version,
				To: msg.To.String(),
				From: msg.From.String(),
				Nonce: msg.Nonce,
				Value: msg.Value.String(),
				GasLimit: msg.GasLimit,
				GasFeeCap: msg.GasFeeCap.String(),
				GasPremium: msg.GasPremium.String(),
				Method: msg.Method.String(),
				Params: hex.EncodeToString(msg.Params),
				CID: ccid,
			}
			jsonBytes, err := json.Marshal(outmsg)
			if err != nil {
				m.Lloger.Debugf("message parse json error: %s\n", err)
				continue
			}
			jsonString := string(jsonBytes)
			m.Lloger.Debugf("message publish: %s\n", jsonString)
			err = rdb.Publish(context.Background(), mqTopic , jsonString).Err()
			if err != nil {
				m.Lloger.Debugf("message publish redis mq error: %s\n", err)
				continue
			}
		}
	}
}


func (m *Monitor) fetchBlockMessages(conn *SyncerConnection, cIds []cid.Cid) []*types.Message {
	aapi := conn.API()
	var out = make([]*types.Message, 0)
	for _, id := range cIds {
		t, err := aapi.ChainGetMessage(context.Background(), id)
		if err != nil {
			m.Lloger.Errorf("fetch block message by CID: %s, error: %s\n", id.String(), err)
			continue
		}
		out = append(out, t)
	}
	return out
}